package panels;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JPanel;

import classes.Algebra;

public class JGraphPanel extends JPanel {

	/**
	 * JGraph - Used for drawing graph panels
	 */
	private static final long serialVersionUID = 1L;

	private		static float[] dashs	= new float[]	{3f, 5f};
	private		static float[] dots		= new float[]	{1f, 5f};
	private		static float[] solid	= new float[]	{999f, 1f};
	
	private		ArrayList<float[]>	data;
	
	private		int		xMargin;
	private		int		yMargin;
	private		String	xLabel;
	private		String	yLabel;

	private		Color	backgroundColor;
	private		Color	marginColor;
	private		Color	lineColor;
	private		Color	gridColor;
	private		Color	pointColor;
	
	@SuppressWarnings("unused")
	private		BasicStroke	lineStroke;
	
	private		float	scaleX = 1f;
	private		float	scaleY = 1f;
	private		float	gridDx	= 100;
	private		float	gridDy	= 100;
	private		float	origonX = 0f;
	private		float	origonY = 0f;
	
	private 	boolean	gridColsVisible = true;
	private		boolean	gridRowsVisible = true;
	
	private		int		pointRadius = 1;
	
	
	public JGraphPanel() {
		
		data = new ArrayList<float[]>();
		
		setOrigonX(0f);
		setOrigonY(0f);
		
		setXMargin(50);
		setYMargin(30);
		
		setScaleX(1f);
		setScaleY(1f);
		
		setGridDx(60f);
		setGridDy(60f);
		
		setPointRadius(5);
		
		setBackground(Color.black);
		setMarginColor(Color.white);
		setGridColor(Color.green);
		setPointColor(Color.red);
		setLineColor(Color.red);
		
		setXLabel("X-Axis");
		setYLabel("Y-Axis");
		
		this.setDoubleBuffered(true);
		testRun();
	}
	
	private static BasicStroke	getSolidStroke() {
		return new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, solid, 2f);
	}
	@SuppressWarnings("unused")
	private static BasicStroke getDashStroke() {
		return new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dashs, 2f);
	}
	private static BasicStroke getDotStroke() {
		return new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dots, 2f);
	}
	
	private int	textWidth(String S){
		return 40;
	}
	private int textHeight(String S) {
		return 10;
	}
	
	private	int	realX(float x) {	return getXMargin() + (int) ((x-getOrigonX()) / getScaleX());					}
	private int	realY(float y) {	return getHeight()	- (int) (getYMargin() + (y-getOrigonY()) / getScaleY());	}

	@SuppressWarnings("unused")
	private void graphText(Graphics G, String S, float x, float y) {
		G.drawString(S, realX(x), realY(y));
	}
	
	private void graphPoint(Graphics G, float x, float y) {
		G.drawArc(
			realX(x)-getPointRadius(), 
			realY(y)-getPointRadius(), 
			getPointRadius() * 2, 
			getPointRadius() * 2, 
			0, 
			360
		);
	}
	
	@SuppressWarnings("unused")
	private void graphFill(Graphics G, float x, float y, float w, float h) {
		G.fillRect(
			realX(x),
			realY(y),
			(int) (w / getScaleX()),
			(int) (h / getScaleY())
		);
	}
	
	private void graphLine(Graphics G, float x1, float y1, float x2, float y2) {
		//
		// This will draw the line on the graph. NOTE that graph coordinates are NOT
		// the same as real coordinates
		//
		G.drawLine(
			realX(x1), 
			realY(y1),
			realX(x2),
			realY(y2)
		);
	}
	
	private void fillWindow(Graphics G) {
		G.fillRect(0,0,getWidth(), getHeight());
	}
	
	private void drawMargins(Graphics G) {
		//
		// Draw the graph boundary lines
		//
		graphLine(G, getOrigonX(), getOrigonY(), getOrigonX(), getGraphSpaceY()+getOrigonY() );
		graphLine(G, getOrigonX(), getOrigonY(), getGraphSpaceX()+getOrigonX(), getOrigonY() );
	}
	
	private void drawGridCols(Graphics G) {
		//
		// Draw the grid lines
		//
		float	col;
		for(col=getGridDx()+getOrigonX(); col<=getGraphSpaceX()+getOrigonX(); col += getGridDx()) {
			graphLine(G, col, getOrigonY(), col, getGraphSpaceY() + getOrigonY());
		}
	}
	
	private void drawGridRows(Graphics G) {
		//
		// Draw the grid lines
		//
		float	row;
		for(row=getGridDy()+getOrigonY(); row<=getGraphSpaceY()+getOrigonY(); row += getGridDy()) {
			graphLine(G, 0, row, getGraphSpaceX()+getOrigonX(), row);
		}
	}
	
	private void drawGridLines(Graphics G) {
		if (isGridColsVisible())
			drawGridCols(G);
		if (isGridRowsVisible())
			drawGridRows(G);
	}
	
	private void drawGridRowLineLabels(Graphics G) {
		//
		// Draw the grid lines
		//
		float	row;
		int		x, y;
		String	S;
		x = getXMargin() - textWidth("x");
		
		for(row=getOrigonY(); row<=getGraphSpaceY()+getOrigonY(); row += getGridDy()) {
			S = Float.toString(row);
			y = (int) realY(row) + textHeight(S)/2;
			G.drawString( S, x, y);
		}
	}
	
	private void drawGridColLineLabels(Graphics G) {
		//
		// Draw the grid lines
		//
		float	col;
		int		x, y;
		String	S;
		y = getHeight() - textHeight("x");
		
		for(col=getOrigonX(); col<=getGraphSpaceX()+getOrigonX(); col += getGridDx()) {
			S = Float.toString(col);
			x = (int) realX(col) - textWidth(S)/2;
			G.drawString( S, x, y);
		}
	}
	
	private void drawGridLineLabels(Graphics G) {
		drawGridRowLineLabels(G);
		drawGridColLineLabels(G);
	}
	
	private void drawXlabel(Graphics G) {
		G.drawString(getXLabel(), (getWidth()/2) - (textWidth(getXLabel())/2), getGraphNorth());
	}
	
	private void drawYlabel(Graphics G) {
		G.drawString(getYLabel(), getGraphEast(), (getHeight()/2) + textHeight(getXLabel())/2);
	}
	
	private void drawLabels(Graphics G) {
		drawXlabel(G);
		drawYlabel(G);
	}
	
	private	void drawGraphPoints(Graphics G) {
		
		if (data!=null) {
			Iterator<float[]>	itr = data.iterator();
			float				P[];
			
			while(itr.hasNext()) {
				P = itr.next();
				graphPoint(G,P[0],P[1]);
			}
		}
	}

	private void drawGraphLines(Graphics G) {
		//
		// Ensure that the data is sorted first
		//
		int		row = 0;
		float	P1[], P2[];
		
		for(row=0; row<data.size()-1; row++) {
			P1 = data.get(row);
			P2 = data.get(row+1);
			graphLine(G, P1[0], P1[1], P2[0], P2[1]);
		}
	}
	
	
	@Override
	public void paint(Graphics G) {
		Graphics2D g2d = (Graphics2D) G;
		
		//
		// Clear the screen
		//
		setWindowClip(G);
		G.setColor(getBackgroundColor());
		fillWindow(G);

		//
		// Draw the margin lines
		//
		G.setColor(getMarginColor());
		setWindowClip(G);
		g2d.setStroke(getSolidStroke());
		drawMargins(G);
		
		//
		// Draw the grid lines
		//
		setGraphClip(G);
		G.setColor(getGridColor());
		g2d.setStroke(getDotStroke());
		drawGridLines(G);
		
		//
		// Draw the labels for the axis
		//
		setWindowClip(G);
		G.setColor(getMarginColor());
		g2d.setStroke(getSolidStroke());
		drawLabels(G);
		
		//
		// Draw the points
		//
		setGraphClip(G);
		G.setColor(getPointColor());
		g2d.setStroke(getSolidStroke());
		drawGraphPoints(G);
		
		//
		// Draw the graph line labels
		//
		setWindowClip(G);
		G.setColor(getMarginColor());
		g2d.setStroke(getSolidStroke());
		drawGridLineLabels(G);
		
		//
		// draw the graph lines
		//
		setGraphClip(G);
		G.setColor(getLineColor());
		g2d.setStroke(getSolidStroke());
		drawGraphLines(G);
		
	}
	
	private void	setGraphClip(Graphics G) {
		G.setClip(getXMargin(), getYMargin(), getGraphWidth(), getGraphHeight());
	}
	
	private void	setWindowClip(Graphics G) {
		G.setClip(0,0,getWidth(), getHeight());
	}
	
	//
	// Returns drawing window in graphing coordinates
	//
	public int getGraphSouth() {
		return getHeight()-getYMargin();
	}
	public int getGraphEast() {
		return getWidth() - getXMargin();
	}
	public int getGraphNorth() {
		return getYMargin();
	}
	public int getGraphWest() {
		return getXMargin();
	}
	public int getGraphWidth() {
		return getWidth() - 2*getXMargin();
	}
	public int getGraphHeight() {
		return getHeight() - 2*getYMargin();
	}
	
	public float getGraphSpaceX() {
		return getGraphWidth() * getScaleX();
	}
	
	public float getGraphSpaceY() {
		return getGraphHeight() * getScaleY();
	}

	public void setXLabel(String xLabel) {
		if (xLabel==null) xLabel = "";
		this.xLabel = xLabel;
	}

	public String getXLabel() {
		return xLabel;
	}

	public void setYLabel(String yLabel) {
		if (yLabel==null) yLabel = "";
		this.yLabel = yLabel;
	}

	public String getYLabel() {
		return yLabel;
	}

	public void setXMargin(int xMargin) {
		this.xMargin = xMargin;
	}

	public int getXMargin() {
		return xMargin;
	}

	public void setYMargin(int yMargin) {
		this.yMargin = yMargin;
	}

	public int getYMargin() {
		return yMargin;
	}

	public void setGridColor(Color gridColor) {
		this.gridColor = gridColor;
	}

	public Color getGridColor() {
		return gridColor;
	}

	public void setGridDx(float gridDx) {
		this.gridDx = gridDx;
	}

	public float getGridDx() {
		return gridDx;
	}

	public void setGridDy(float gridDy) {
		this.gridDy = gridDy;
	}

	public float getGridDy() {
		return gridDy;
	}

	public void setMarginColor(Color marginColor) {
		this.marginColor = marginColor;
	}

	public Color getMarginColor() {
		return marginColor;
	}

	public void setBackgroundColor(Color backgroundColor) {
		this.backgroundColor = backgroundColor;
	}

	public Color getBackgroundColor() {
		return backgroundColor;
	}

	public void setLineColor(Color lineColor) {
		this.lineColor = lineColor;
	}

	public Color getLineColor() {
		return lineColor;
	}

	public void setScaleX(float scaleX) {
		this.scaleX = scaleX;
	}

	public float getScaleX() {
		return scaleX;
	}

	public void setScaleY(float scaleY) {
		this.scaleY = scaleY;
	}

	public float getScaleY() {
		return scaleY;
	}

	public void setGridColsVisible(boolean gridColsVisible) {
		this.gridColsVisible = gridColsVisible;
	}

	public boolean isGridColsVisible() {
		return gridColsVisible;
	}

	public void setGridRowsVisible(boolean gridRowsVisible) {
		this.gridRowsVisible = gridRowsVisible;
	}

	public boolean isGridRowsVisible() {
		return gridRowsVisible;
	}

	public void setPointRadius(int pointRadius) {
		this.pointRadius = pointRadius;
	}

	public int getPointRadius() {
		return pointRadius;
	}

	public void setPointColor(Color pointColor) {
		this.pointColor = pointColor;
	}

	public Color getPointColor() {
		return pointColor;
	}
	
	public boolean addPoint(float x, float y) {
		float	P[] = new float[] {	x, y };
		return data.add(P);
	}
	
	public boolean removePoint(float x, float y) {
		float	P[] = new float[] {	x, y };
		return	data.remove(P);
	}
	
	public float[][] getMatrix() {
		//
		// This will return a copy of the data in matrix form
		//
		if (data==null || data.size()==0) {
			return null;
		} else {
			float	M[][] = Algebra.newMatrix(data.size(), 2);
			int		row;
			
			for(row=0; row<Algebra.numRows(M); row++) {
				M[row] = Algebra.copy(data.get(row));
			}
			return M;
		}
	}
	
	public void clearData() {
		data.clear();
	}
	
	public void addMatrix(float M[][]) {
		if (data != null) {
			int row=0;
			for(row=0; row<Algebra.numRows(M); row++) {
				data.add(Algebra.copy(M[row]));
			}
		}
	}
	
	@SuppressWarnings("unused")
	private void sortByX() {
		//
		// This will sort the data by the x variable
		//
		float	M[][] = getMatrix();
		if (M!=null) {
			M = Algebra.sort(0, M);
			clearData();
			addMatrix(M);
		}
	}
	
	@SuppressWarnings("unused")
	private void sortByY() {
		//
		// This will sort the data by the x variable
		//
		float	M[][] = getMatrix();
		if (M!=null) {
			M = Algebra.sort(1, M);
			clearData();
			addMatrix(M);
		}
	}
	
	public float getOrigonX() {
		return origonX;
	}

	public void setOrigonX(float origonX) {
		this.origonX = origonX;
	}

	public float getOrigonY() {
		return origonY;
	}

	public void setOrigonY(float origonY) {
		this.origonY = origonY;
	}
	
	public void testRun() {
		float	x, y, a;
		
		for(a=0; a < 360f; a+= 12.5f) {
			x = 200f + (float) (Math.sin(Math.toRadians( a )) * 100);
			y = 200f + (float) (Math.cos(Math.toRadians( a )) * 100);
			addPoint(x,y);
		}
		
		setOrigonX(100f);
		setOrigonY(100f);
//		sortByY();
	}

	
}
